/*
 * HSM Proxy Project.
 * Copyright (C) 2013 FedICT.
 * Copyright (C) 2013 Frank Cornelis.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.hsm.ws.impl;

import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.jws.HandlerChain;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.JAXBElement;
import javax.xml.ws.BindingType;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.soap.SOAPBinding;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import be.fedict.hsm.model.SignatureService;
import be.fedict.hsm.ws.DSSConstants;
import be.fedict.hsm.ws.ResultMajor;
import be.fedict.hsm.ws.jaxb.dss.AnyType;
import be.fedict.hsm.ws.jaxb.dss.Base64Signature;
import be.fedict.hsm.ws.jaxb.dss.DocumentHash;
import be.fedict.hsm.ws.jaxb.dss.InputDocuments;
import be.fedict.hsm.ws.jaxb.dss.KeySelector;
import be.fedict.hsm.ws.jaxb.dss.ObjectFactory;
import be.fedict.hsm.ws.jaxb.dss.ResponseBaseType;
import be.fedict.hsm.ws.jaxb.dss.Result;
import be.fedict.hsm.ws.jaxb.dss.SignRequest;
import be.fedict.hsm.ws.jaxb.dss.SignResponse;
import be.fedict.hsm.ws.jaxb.dss.SignatureObject;
import be.fedict.hsm.ws.jaxb.dss.VerifyRequest;
import be.fedict.hsm.ws.jaxb.hsm.GetAliasesRequest;
import be.fedict.hsm.ws.jaxb.hsm.GetCertificateChainRequest;
import be.fedict.hsm.ws.jaxb.xmldsig.DigestMethodType;
import be.fedict.hsm.ws.jaxb.xmldsig.KeyInfoType;
import be.fedict.hsm.ws.jaxb.xmldsig.X509DataType;
import be.fedict.hsm.ws.jaxws.DigitalSignatureServicePortType;

@WebService(endpointInterface = "be.fedict.hsm.ws.jaxws.DigitalSignatureServicePortType", targetNamespace = "urn:be:fedict:hsm-proxy:ws", serviceName = "DigitalSignatureService")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
@HandlerChain(file = "/hsm-proxy-ws-handlers.xml")
public class DigitalSignatureServicePortImpl implements
		DigitalSignatureServicePortType {

	private static final Log LOG = LogFactory
			.getLog(DigitalSignatureServicePortImpl.class);

	@EJB
	private SignatureService signatureService;

	@Resource
	private WebServiceContext webServiceContext;

	private final ObjectFactory objectFactory;

	private final be.fedict.hsm.ws.jaxb.xmldsig.ObjectFactory xmldsigObjectFactory;

	public DigitalSignatureServicePortImpl() {
		this.objectFactory = new ObjectFactory();
		this.xmldsigObjectFactory = new be.fedict.hsm.ws.jaxb.xmldsig.ObjectFactory();
	}

	@Override
	@WebMethod
	@WebResult(name = "VerifyResponse", targetNamespace = "urn:oasis:names:tc:dss:1.0:core:schema", partName = "VerifyResponse")
	public ResponseBaseType verify(
			@WebParam(name = "VerifyRequest", targetNamespace = "urn:oasis:names:tc:dss:1.0:core:schema", partName = "VerifyRequest") VerifyRequest verifyRequest) {
		return null;
	}

	@Override
	@WebMethod
	@WebResult(name = "SignResponse", targetNamespace = "urn:oasis:names:tc:dss:1.0:core:schema", partName = "SignResponse")
	public SignResponse sign(
			@WebParam(name = "SignRequest", targetNamespace = "urn:oasis:names:tc:dss:1.0:core:schema", partName = "SignRequest") SignRequest signRequest) {
		LOG.debug("sign");
		Principal userPrincipal = this.webServiceContext.getUserPrincipal();
		if (null != userPrincipal) {
			String userName = userPrincipal.getName();
			LOG.debug("username: " + userName);
		} else {
			LOG.debug("no user principal");
		}
		String requestId = signRequest.getRequestID();
		String digestMethodAlgorithm = null;
		byte[] digestValue = null;
		String keyAlias = null;
		InputDocuments inputDocuments = signRequest.getInputDocuments();
		if (null == inputDocuments) {
			LOG.error("missing dss:InputDocuments");
			return errorSignResponse(ResultMajor.REQUESTER_ERROR);
		}
		List<Object> inputDocumentsContent = inputDocuments
				.getDocumentOrTransformedDataOrDocumentHash();
		for (Object object : inputDocumentsContent) {
			if (object instanceof DocumentHash) {
				DocumentHash documentHash = (DocumentHash) object;
				DigestMethodType digestMethod = documentHash.getDigestMethod();
				digestMethodAlgorithm = digestMethod.getAlgorithm();
				digestValue = documentHash.getDigestValue();
			}
		}
		AnyType optionalInputs = signRequest.getOptionalInputs();
		if (null == optionalInputs) {
			LOG.error("missing dss:OptionalInputs");
			return errorSignResponse(ResultMajor.REQUESTER_ERROR);
		}
		List<Object> optionalInputsContent = optionalInputs.getAny();
		for (Object object : optionalInputsContent) {
			if (object instanceof KeySelector) {
				KeySelector keySelector = (KeySelector) object;
				KeyInfoType keyInfo = keySelector.getKeyInfo();
				List<Object> keyInfoContent = keyInfo.getContent();
				for (Object keyInfoObject : keyInfoContent) {
					if (keyInfoObject instanceof JAXBElement) {
						JAXBElement jaxbElement = (JAXBElement) keyInfoObject;
						keyAlias = (String) jaxbElement.getValue();
					}
				}
			}
		}

		if (null == digestMethodAlgorithm) {
			LOG.error("missing digest algo");
			return errorSignResponse(ResultMajor.REQUESTER_ERROR);
		}
		if (null == digestValue) {
			LOG.error("missing digest value");
			return errorSignResponse(ResultMajor.REQUESTER_ERROR);
		}
		if (null == keyAlias) {
			LOG.error("missing dss:KeySelector");
			return errorSignResponse(ResultMajor.REQUESTER_ERROR);
		}
		LOG.debug("digest algo: " + digestMethodAlgorithm);
		LOG.debug("key alias: " + keyAlias);

		byte[] signatureValue;
		try {
			signatureValue = this.signatureService.sign(digestMethodAlgorithm,
					digestValue, keyAlias);
		} catch (NoSuchAlgorithmException e) {
			return errorSignResponse(ResultMajor.REQUESTER_ERROR);
		}

		SignResponse signResponse = this.objectFactory.createSignResponse();
		signResponse.setRequestID(requestId);
		signResponse.setProfile(DSSConstants.HSM_PROXY_DSS_PROFILE_URI);

		Result result = this.objectFactory.createResult();
		signResponse.setResult(result);
		result.setResultMajor(ResultMajor.SUCCESS.getUri());
		result.setResultMinor(DSSConstants.RESULT_MINOR_VALID_ON_ALL_DOCS);

		SignatureObject signatureObject = this.objectFactory
				.createSignatureObject();
		signResponse.setSignatureObject(signatureObject);
		Base64Signature base64Signature = this.objectFactory
				.createBase64Signature();
		signatureObject.setBase64Signature(base64Signature);
		base64Signature.setValue(signatureValue);

		return signResponse;
	}

	private SignResponse errorSignResponse(ResultMajor resultMajor) {
		SignResponse signResponse = this.objectFactory.createSignResponse();
		Result result = this.objectFactory.createResult();
		signResponse.setResult(result);
		result.setResultMajor(resultMajor.getUri());
		return signResponse;
	}

	private ResponseBaseType errorResponse(ResultMajor resultMajor) {
		ResponseBaseType response = this.objectFactory.createResponseBaseType();
		Result result = this.objectFactory.createResult();
		response.setResult(result);
		result.setResultMajor(resultMajor.getUri());
		return response;
	}

	@Override
	@WebMethod(operationName = "get-aliases")
	@WebResult(name = "Response", targetNamespace = "urn:oasis:names:tc:dss:1.0:core:schema", partName = "GetAliasesResponse")
	public ResponseBaseType getAliases(
			@WebParam(name = "GetAliasesRequest", targetNamespace = "urn:be:fedict:hsm-proxy:ws:dss:profiles:hsm-proxy:1.0", partName = "GetAliasesRequest") GetAliasesRequest getAliasesRequest) {
		String requestId = getAliasesRequest.getRequestID();
		Set<String> aliases = this.signatureService.getAliases();
		ResponseBaseType response = this.objectFactory.createResponseBaseType();
		response.setRequestID(requestId);
		response.setProfile(DSSConstants.HSM_PROXY_DSS_PROFILE_URI);

		Result result = this.objectFactory.createResult();
		response.setResult(result);
		result.setResultMajor(ResultMajor.SUCCESS.getUri());

		AnyType optionalOutputs = this.objectFactory.createAnyType();
		response.setOptionalOutputs(optionalOutputs);
		for (String alias : aliases) {
			KeySelector keySelector = this.objectFactory.createKeySelector();
			optionalOutputs.getAny().add(keySelector);
			KeyInfoType keyInfo = this.xmldsigObjectFactory.createKeyInfoType();
			keySelector.setKeyInfo(keyInfo);
			keyInfo.getContent().add(
					this.xmldsigObjectFactory.createKeyName(alias));
		}
		response.setOptionalOutputs(optionalOutputs);

		return response;
	}

	@Override
	@WebMethod(operationName = "get-certificate-chain")
	@WebResult(name = "Response", targetNamespace = "urn:oasis:names:tc:dss:1.0:core:schema", partName = "GetCertificateChainResponse")
	public ResponseBaseType getCertificateChain(
			@WebParam(name = "GetCertificateChainRequest", targetNamespace = "urn:be:fedict:hsm-proxy:ws:dss:profiles:hsm-proxy:1.0", partName = "GetCertificateChainRequest") GetCertificateChainRequest getCertificateChainRequest) {
		String requestId = getCertificateChainRequest.getRequestID();
		AnyType optionalInputs = getCertificateChainRequest.getOptionalInputs();
		if (null == optionalInputs) {
			LOG.error("missing dss:OptionalInputs");
			return errorResponse(ResultMajor.REQUESTER_ERROR);
		}
		List<Object> optionalInputsContent = optionalInputs.getAny();
		String alias = null;
		for (Object object : optionalInputsContent) {
			if (object instanceof KeySelector) {
				KeySelector keySelector = (KeySelector) object;
				KeyInfoType keyInfo = keySelector.getKeyInfo();
				if (null == keyInfo) {
					LOG.error("missing ds:KeyInfo");
					return errorResponse(ResultMajor.REQUESTER_ERROR);
				}
				List<Object> keyInfoContent = keyInfo.getContent();
				for (Object keyInfoObject : keyInfoContent) {
					if (keyInfoObject instanceof JAXBElement) {
						JAXBElement jaxbElement = (JAXBElement) keyInfoObject;
						alias = (String) jaxbElement.getValue();
					}
				}
			}
		}
		if (null == alias) {
			LOG.error("missing dss:KeySelector/ds:KeyInfo/ds:KeyName");
			return errorResponse(ResultMajor.REQUESTER_ERROR);
		}
		LOG.debug("get certificate chain for alias: " + alias);
		Certificate[] certificateChain;
		try {
			certificateChain = this.signatureService.getCertificateChain(alias);
		} catch (NoSuchAlgorithmException e) {
			LOG.error("no such algo: " + e.getMessage());
			return errorResponse(ResultMajor.REQUESTER_ERROR);
		}
		if (null == certificateChain) {
			LOG.error("no cert chain found");
			return errorResponse(ResultMajor.REQUESTER_ERROR);
		}
		ResponseBaseType response = this.objectFactory.createResponseBaseType();
		response.setRequestID(requestId);
		response.setProfile(DSSConstants.HSM_PROXY_DSS_PROFILE_URI);

		Result result = this.objectFactory.createResult();
		response.setResult(result);
		result.setResultMajor(ResultMajor.SUCCESS.getUri());

		KeyInfoType keyInfo = this.xmldsigObjectFactory.createKeyInfoType();
		AnyType optionalOutputs = this.objectFactory.createAnyType();
		optionalOutputs.getAny().add(
				this.xmldsigObjectFactory.createKeyInfo(keyInfo));
		response.setOptionalOutputs(optionalOutputs);

		List<Object> keyInfoContent = keyInfo.getContent();
		X509DataType x509Data = this.xmldsigObjectFactory.createX509DataType();
		keyInfoContent.add(this.xmldsigObjectFactory.createX509Data(x509Data));

		List<Object> x509DataContent = x509Data
				.getX509IssuerSerialOrX509SKIOrX509SubjectName();
		for (Certificate certificate : certificateChain) {
			try {
				x509DataContent.add(this.xmldsigObjectFactory
						.createX509DataTypeX509Certificate(certificate
								.getEncoded()));
			} catch (CertificateEncodingException e) {
				LOG.error("certificate encoding error: " + e.getMessage());
				return errorResponse(ResultMajor.RESPONDER_ERROR);
			}
		}

		return response;
	}
}
